/****************************************************************************
 *   oboe.core.SigSlot							 							*
 *   Copyright (C) 2011 by Felipe Manga										*
 *   lufeboma@gmail.com														*
 *									 										*
 *   Permission is hereby granted, free of charge, to any person obtaining 	*
 *   a copy of this software and associated documentation files (the	   	*
 *   "Software"), to deal in the Software without restriction, including   	*
 *   without limitation the rights to use, copy, modify, merge, publish,   	*
 *   distribute, sublicense, and/or sell copies of the Software, and to		*
 *   permit persons to whom the Software is furnished to do so, subject to 	*
 *   the following conditions:						 						*
 *									 										*
 *   The above copyright notice and this permission notice shall be			*
 *   included in all copies or substantial portions of the Software.	   	*
 *									 										*
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,	   	*
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF		*
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR	 	*
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 	*
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 	*
 *   OTHER DEALINGS IN THE SOFTWARE.					   					*
 ****************************************************************************/

package oboe.core;


import haxe.Log;
import haxe.rtti.Meta;

import oboe.core.SigSlotCollection;

enum SlotType
{
	SLOT(obj:ISigSlot);
	PIPE;
	PIPE_SLOT(obj:ISigSlot);
	VA_SLOT(obj:ISigSlot);
	VA_PIPE_SLOT(obj:ISigSlot);
}

class SigSlot extends SigSlotCollection, implements ISigSlot
{
	// var KungFuDeathGrip:Array<ISigSlot>;
	public var sigslotParent(default, null):SigSlot;
	// private var signals:Hash< String >;
	private var sigslotOwner:ISigSlot;
	private var sigslot:SigSlot;

	private var slotNames:Hash<SlotType>;
	private var isRooted:Bool;
	
	public function new()
	{
		super();
		// this.KungFuDeathGrip = new Array<ISigSlot>();
		if ( Oboe.root == null ) Oboe.root = this;
		SigSlot.initSigSlot( this, this );
	}
	
	public inline function init( owner:ISigSlot ):Void
	{
		SigSlot.initSigSlot( owner, this );
	}
	
	public static function initSigSlot( owner:ISigSlot, child:ISigSlot ):Void
	{
		var isig:ISigSlot = child;
		while ( !Std.is( isig, SigSlot ) )
		{
			isig = Reflect.field( isig, "sigslot" );
		}
		Reflect.setField( owner, "sigslot", isig );
		cast(isig, SigSlot).addOwner( owner );
		// cast(isig, SigSlot).KungFuDeathGrip.push( owner );
	}
	
	private inline function addOwner( owner:ISigSlot ):Void
	{
		this.sigslotOwner = owner;
		var c:String = this.getFinalClassName( owner );
		if( this.slotNames == null ) this.slotNames = new Hash<SlotType>();
		getSlotsFrom( owner );
	}
	
	public function slots():Iterator<String> return slotNames.keys()
	
	private function getSlotsRec( c:Class<Dynamic>, obj ):Void
	{
		var sc = Type.getSuperClass(c);
		if ( sc != null )
		{
			getSlotsRec( sc, obj );
		}
		var fields = Meta.getFields( c );

		for( k in Reflect.fields(fields) )
		{
			var name:String = k;
			var val = Reflect.field( fields, name );
			var isSlot:Bool = Reflect.hasField( val, "slot" );
			var isPipe:Bool = Reflect.hasField( val, "pipe" );
			var isVarArg:Bool = Reflect.hasField( val, "vararg" );
			if ( isVarArg )
			{
				if ( isSlot && isPipe )
				{
					slotNames.set( name, VA_PIPE_SLOT(obj) );
				}
				else if( isSlot )
				{
					slotNames.set( name, VA_SLOT(obj) );
				}
				else if( isPipe )
				{
					slotNames.set( name, PIPE );
				}
			}
			else
			{
				if ( isSlot && isPipe )
				{
					slotNames.set( name, PIPE_SLOT(obj) );
				}
				else if( isSlot )
				{
					slotNames.set( name, SLOT(obj) );
				}
				else if( isPipe )
				{
					slotNames.set( name, PIPE );
				}
			}
		}
	}
	
	private inline function getSlotsFrom( obj:ISigSlot ):Void
	{
		this.getSlotsRec( Type.getClass( obj ), obj );
	}
	

	private function selfDestruct():Void
	{
		if ( this.sigslotParent != null )
		{
			this.sigslotParent.removeChild( this );
		}
	}
	
	public override function addChild( ichild:ISigSlot ):Void
	{
		var child:SigSlot = Reflect.field(ichild, "sigslot");
		
		if ( child == null || child == this || child.sigslotParent == this ) return;
		
		super.addChild( child );

		if( child.sigslotParent != null )
		{
			child.sigslotParent.removeChild( ichild );
		}

		child.sigslotParent = this;
		child.message("onAddToParent", []);
	}
	
	@slot function __setRootedSigSlot( v:Bool ):Void
	{
		if ( this.isRooted == v ) return;
		this.isRooted = v;
		if ( v ) Oboe.global.addChild( this );
		else Oboe.global.removeChild( this );
		this.signalDown("__setRootedSigSlot", [v] );
	}
	
	@slot function onAddToParent():Void
	{
		if ( this.sigslotParent != null ) 
		{
			this.__setRootedSigSlot( this.sigslotParent.isRooted );
		}
	}
	
	@slot function onRemoveFromParent():Void
	{
		__setRootedSigSlot( false );
	}

	public override function removeChild( ichild:ISigSlot ):Void
	{
		var child:SigSlot = Reflect.field(ichild, "sigslot");
		super.removeChild( child.sigslotOwner );
		if( child == null || child.sigslotParent != this ) return;
		
		child.message("onRemoveFromParent", []);
		child.sigslotParent = null;
	}
	
	public #if !debug inline #end function message( name:String, arguments:Array<Dynamic> ):Void
	{
		var v = this.slotNames.get( name );
		if ( v != null )
		{
			switch (v) 
			{
				case PIPE:
					this.signalUp( name, arguments );
				case SLOT(obj):
					Reflect.callMethod( obj, Reflect.field( obj, name ), arguments );
				case PIPE_SLOT(obj):
					Reflect.callMethod( obj, Reflect.field( obj, name ), arguments );
					this.signalUp( name, arguments );
				case VA_SLOT(obj):
					Reflect.field( obj, name )( arguments );
				case VA_PIPE_SLOT(obj):
					Reflect.field( obj, name )( arguments );
					this.signalUp( name, arguments );
				default:
			}
		}
	}
	
	public function _message( name:String, dir:SignalDirection, arguments:Array<Dynamic> ):Void
	{
		var v = this.slotNames.get( name );
		if ( v == null ) return;
		switch (v) 
		{
			case PIPE:
				if ( dir == SignalDirection.SIG_DOWN ) return;
				this.sigslotParent._message( name, SignalDirection.SIG_UP, arguments );
				this.sigslotParent.signalDown( name, arguments );
			case SLOT(obj):
				Reflect.callMethod( obj, Reflect.field( obj, name ), arguments );
			case PIPE_SLOT(obj):
				if ( dir == SignalDirection.SIG_DOWN ) return;
				Reflect.callMethod( obj, Reflect.field( obj, name ), arguments );
				this.sigslotParent._message( name, SignalDirection.SIG_UP, arguments );
				this.sigslotParent.signalDown( name, arguments );
			case VA_SLOT(obj):
				Reflect.field( obj, name )( arguments );
			case VA_PIPE_SLOT(obj):
				if ( dir == SignalDirection.SIG_DOWN ) return;
				Reflect.field( obj, name )( arguments );
				this.sigslotParent._message( name, SignalDirection.SIG_UP, arguments );
				this.sigslotParent.signalDown( name, arguments );
			default:
		}
	}	
	
	public #if !debug inline #end function signalUp( name:String, arguments:Array<Dynamic> ):Void
	{
		if ( this.sigslotParent != null )
		{
			this.sigslotParent._message( name, SignalDirection.SIG_UP, arguments );
			this.sigslotParent.signalDown( name, arguments );
		}
	}

	public #if !debug inline #end function shortSignal( name:String, arguments:Array<Dynamic> ):Void
	{
		if ( this.sigslotParent != null )
		{
			this.sigslotParent._message( name, SignalDirection.SIG_UP, arguments );
			this.sigslotParent.signalDown( name, arguments );
		}
		else
		{
			this._message( name, SignalDirection.SIG_DOWN, arguments );
		}
		this.signalDown( name, arguments );
	}
	
	public #if !debug inline #end function broadcastSignal( name:String, arguments:Array<Dynamic> ):Void
	{
		/* * /
		if ( Oboe.root == null ) throw "Broadcast with no root sigslot";
		Reflect.field( Oboe.root, "sigslot" ).signal( name, arguments );
		/*/
		Oboe.global.signalDown( name, arguments );
		/**/
	}
	
	public function signal( name:String, arguments:Array<Dynamic> )
	{
		this.message( name, arguments );
		for ( child in children )
		{
			cast(child, SigSlot).signal( name, arguments );
		}
	}

	
	
	/*
	public function slot__getSigSlotInfo( children:Object, pipes:Object, slots:Object, signals:Object ):Void
	{
		var c:TypedDictionary = this._getSigSlotChildren();
		var s:TypedDictionary = getSlots();
		
		for( child in c )
		{
			var type:String = Type.typeof(child);
			if( Reflect.field(children, type) )
			{
				children[ type ] = [];
			}
			children[ type ].push( child );
		}
		
		for( pipe in this.pipes )
		{
			pipes[ pipe ] = pipe;
		}
		
		for( signal in this.signals )
		{
			signals[ signal ] = signal;
		}
		
		for( slot in s )
		{
			if( slot.charAt(0) != "_" )
			{
				slots[slot] = slot;
			}
		}
	}

	private function getSigSlotParent():SigSlot
	{
		return this.sigslotParent;
	}

	private function addPipe( name:String, func:Function ):Void
	{
		if( !this.pipes ) this.pipes = new Dictionary();
		this.pipes[ name ] = func;
	}

	private static inline var SIG_CHILDREN:Int = 1;
	private static inline var SIG_PARENT:Int = 2;
	private static inline var SIG_RECURSIVE:Int = 5;

	private function _emitSignal( name:String, sigtype:Int, arguments:Array ):Void
	{
		var slotName:String = "slot_" + name;
		
		if( sigtype&SIG_PARENT )
		{
		if( this.sigslotParent && this.pipes && name in this.pipes )
		{
			this.pipes[name].apply( this, arguments );
			this.sigslotParent._emitSignal( name, SIG_PARENT+SIG_CHILDREN, arguments ); 
		}
		else
		{
			this.messageEx( name, arguments );
		}
		}
		else
		{
		if( isNaN(Number(Oboe.sigMiss[ name ])) ) Oboe.sigMiss[ name ] = 0;
		Oboe.sigMiss[ name ]++;
		}
		
		if( sigtype&SIG_CHILDREN )
		{
		if( (sigtype&SIG_RECURSIVE) == SIG_RECURSIVE )
		{
			var children:Dictionary = this._getSigSlotChildren();
			for each( var child:SigSlot in children )
			{
			child._emitSignal( name, SIG_PARENT+SIG_RECURSIVE, arguments );
			}
		}
		else 
		{
			super.signalDownEx( name, arguments )
		}
		}
	}

	public override function message( name:String, ... arguments ):Void
	{
		super.messageEx( name, arguments );
		if( this.sigslotParent && this.pipes && name in this.pipes  )
		{
			this.pipes[name].apply( this, arguments );
			this.sigslotParent._emitSignal( name, SIG_PARENT+SIG_CHILDREN, arguments ); 
		}
	}

	private function signalUp( name:String, ... arguments ):Void
	{
		signals[ name ] = name;
		if( this.sigslotParent )
		{
			this.sigslotParent._emitSignal( name, SIG_PARENT+SIG_CHILDREN, arguments ); 
		}
	}

	private function shortSignal( name:String, ... arguments ):Void
	{		
		signals[ name ] = name;
		if( this.sigslotParent )
		{
			this.sigslotParent._emitSignal( name, SIG_PARENT+SIG_CHILDREN, arguments ); 
			this.signalDownEx( name, arguments );
		}
		else
		{
			this.messageEx( name, arguments );
			this.signalDownEx( name, arguments );
		}
	}

	private function signal( name:String, ... arguments ):Void
	{
		signals[ name ] = name;
		if( this.sigslotParent )
		{
		this.sigslotParent._emitSignal( name, SIG_PARENT+SIG_RECURSIVE, arguments ); 
		}
		else
		{
		this._emitSignal( name, SIG_PARENT+SIG_RECURSIVE, arguments );
		}
	}

	private function broadcastSignal( name:String, ...arguments ):Void
	{
		signals[ name ] = name;
		var root:SigSlot = this;
		while( root.sigslotParent )
		{
		root = root.sigslotParent;
		}
		
		root._emitSignal( name, SIG_PARENT+SIG_RECURSIVE, arguments );
	}





	private function _signalUp( name:String, ... arguments ):Void
	{
		signals[ name ] = name;
		arguments.unshift( name );
		this.signalUp.apply( this, arguments );
	}

	private function _signalDown( name:String, ... arguments ):Void
	{
		signals[ name ] = name;
		arguments.unshift( name );
		this.signalDown.apply( this, arguments );
	}

	private function _shortSignal( name:String, ... arguments ):Void
	{
		signals[ name ] = name;
		arguments.unshift( name );
		this.shortSignal.apply( this, arguments );
	}

	private function _signal( name:String, ... arguments ):Void
	{
		signals[ name ] = name;
		arguments.unshift( name );
		this.signal.apply( this, arguments );
	}

	private function _broadcastSignal( name:String, ...arguments ):Void
	{
		signals[ name ] = name;
		arguments.unshift( name );
		this.broadcastSignal.apply( this, arguments );
	}
*/
}